package com.childenglish.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
import java.util.Date;
import com.fasterxml.jackson.databind.ObjectMapper;

@Service
public class FileStorageService {

    private static final Logger logger = LoggerFactory.getLogger(FileStorageService.class);

    @Value("${file.upload-dir:uploads}")
    private String uploadDir;
    
    /**
     * 获取上传目录路径（供Controller使用）
     */
    public String getUploadDir() {
        return uploadDir;
    }

    @Value("${file.max-size:10485760}")
    private long maxFileSize;

    @Value("${file.cleanup-enabled:true}")
    private boolean cleanupEnabled;

    @Value("${file.cleanup-age:86400000}")
    private long cleanupAge;

    @Autowired
    private com.childenglish.mapper.FileInfoMapper fileInfoMapper;
    
    @Autowired(required = false)
    @org.springframework.context.annotation.Lazy
    private FileChunkService fileChunkService;

    // 文件名映射缓存（UUID文件名 -> 原始文件名）
    private final Map<String, String> fileNameMapping = new ConcurrentHashMap<>();
    
    // 元数据文件路径
    private Path metadataFilePath;
    
    private final ObjectMapper objectMapper = new ObjectMapper();

    // 允许的文件类型
    private static final List<String> ALLOWED_EXTENSIONS = Arrays.asList(
            "jpg", "jpeg", "png", "gif", "pdf", "doc", "docx", "txt",
            "mp3", "wav", "ogg", "webm", "mp4", "avi", "mov", "m4a"
    );

    // 允许的MIME类型（支持带参数的MIME类型，如 audio/webm; codecs=opus）
    private static final List<String> ALLOWED_MIME_TYPES = Arrays.asList(
            "image/jpeg", "image/png", "image/gif", "image/webp",
            "application/pdf", "application/msword",
            "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
            "text/plain", 
            "audio/mpeg", "audio/mp3", "audio/wav", "audio/ogg", "audio/webm",
            "audio/webm;codecs=opus", "audio/ogg;codecs=opus", "audio/x-m4a",
            "audio/mp4", "video/mp4", "video/webm", "video/avi", "video/quicktime"
    );

    /**
     * 存储文件（重载方法，兼容旧代码）
     */
    public String storeFile(MultipartFile file) throws IOException {
        return storeFile(file, null);
    }

    /**
     * 存储文件并保存到数据库
     */
    public String storeFile(MultipartFile file, String fileType) throws IOException {
        // 先读取文件内容到字节数组，避免多次读取InputStream导致的问题
        byte[] fileBytes;
        try {
            fileBytes = file.getBytes();
        } catch (IOException e) {
            logger.error("读取文件内容失败: {}", file.getOriginalFilename(), e);
            throw new IOException("读取文件内容失败: " + e.getMessage());
        }

        // 安全检查（使用字节数组进行验证）
        validateFile(file, fileBytes);

        // 创建上传目录
        Path uploadPath = Paths.get(uploadDir);
        if (!Files.exists(uploadPath)) {
            Files.createDirectories(uploadPath);
            logger.debug("创建上传目录: {}", uploadPath.toAbsolutePath());
        }

        // 初始化元数据文件路径
        if (metadataFilePath == null) {
            metadataFilePath = uploadPath.resolve(".file-metadata.json");
            // 初始化时加载已有的文件名映射
            loadFileNameMapping();
        }

        // 生成唯一文件名
        String originalFileName = file.getOriginalFilename();
        String fileExtension = getFileExtension(originalFileName).toLowerCase();
        String fileName = UUID.randomUUID().toString() + "." + fileExtension;

        Path filePath = uploadPath.resolve(fileName);

        // 保存文件（使用字节数组）
        Files.write(filePath, fileBytes, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
        
        // 验证文件是否成功保存
        if (!Files.exists(filePath)) {
            logger.error("文件保存失败: 文件不存在 {}", filePath.toAbsolutePath());
            throw new IOException("文件保存失败: 文件不存在");
        }
        
        long savedFileSize = Files.size(filePath);
        if (savedFileSize != fileBytes.length) {
            logger.error("文件保存失败: 大小不匹配 期望: {} bytes, 实际: {} bytes", fileBytes.length, savedFileSize);
            throw new IOException("文件保存失败: 大小不匹配");
        }

        // 保存文件名映射（UUID文件名 -> 原始文件名）
        saveFileNameMapping(fileName, originalFileName);
        
        // 验证映射是否保存成功
        String verifyOriginalName = getOriginalFileName(fileName);
        if (!verifyOriginalName.equals(originalFileName)) {
            logger.error("文件名映射验证失败: 期望={}, 实际={}", originalFileName, verifyOriginalName);
        } else {
            logger.debug("文件名映射验证成功: {} -> {}", fileName, verifyOriginalName);
        }

        logger.debug("文件保存成功: {} -> {}, 大小: {} bytes, 保存后验证: {} bytes", 
                originalFileName, fileName, fileBytes.length, savedFileSize);
        
        // 如果指定了文件类型，保存到数据库
        if (fileType != null && !fileType.isEmpty()) {
            try {
                // 检查数据库中是否已存在该文件
                com.childenglish.entity.FileInfo existingFile = fileInfoMapper.selectByFileName(fileName);
                if (existingFile == null) {
                    com.childenglish.entity.FileInfo fileInfo = new com.childenglish.entity.FileInfo();
                    fileInfo.setFileName(fileName);
                    fileInfo.setOriginalName(originalFileName);
                    fileInfo.setFileSize((long) fileBytes.length);
                    fileInfo.setFileType(fileType); // 使用传入的文件类型
                    fileInfo.setFilePath(Paths.get(uploadDir, fileName).toString());
                    
                    // 设置MIME类型
                    try {
                        String contentType = Files.probeContentType(filePath);
                        fileInfo.setMimeType(contentType != null ? contentType : "application/octet-stream");
                    } catch (Exception e) {
                        fileInfo.setMimeType("application/octet-stream");
                    }
                    
                    fileInfo.setStatus(2); // 合并完成
                    fileInfo.setReviewStatus(0); // 待审核
                    fileInfo.setChunked(0);
                    
                    Date now = new Date();
                    fileInfo.setUploadTime(now);
                    fileInfo.setCompleteTime(now);
                    
                    int insertResult = fileInfoMapper.insert(fileInfo);
                    if (insertResult > 0) {
                        logger.info("文件信息已保存到数据库: fileName={}, fileType={}, id={}", 
                            fileName, fileType, fileInfo.getId());
                    } else {
                        logger.warn("文件信息保存到数据库失败: fileName={}, fileType={}", fileName, fileType);
                    }
                } else {
                    logger.debug("文件已存在于数据库: fileName={}", fileName);
                }
            } catch (Exception e) {
                logger.error("保存文件信息到数据库失败: fileName={}, fileType={}, error={}", 
                    fileName, fileType, e.getMessage(), e);
                // 文件保存成功，数据库保存失败不影响文件上传
            }
        }
        
        return fileName;
    }

    /**
     * 保存文件名映射到元数据文件（公开方法，供其他Service使用）
     */
    public synchronized void saveFileNameMapping(String fileName, String originalName) {
        try {
            // 确保元数据文件路径已初始化
            if (metadataFilePath == null) {
                Path uploadPath = Paths.get(uploadDir);
                if (!Files.exists(uploadPath)) {
                    Files.createDirectories(uploadPath);
                }
                metadataFilePath = uploadPath.resolve(".file-metadata.json");
                logger.debug("初始化元数据文件路径: {}", metadataFilePath);
            }
            
            // 更新内存缓存
            fileNameMapping.put(fileName, originalName);
            
            // 读取现有元数据
            Map<String, String> metadata = loadFileNameMapping();
            metadata.put(fileName, originalName);
            
            // 保存到文件
            String json = objectMapper.writeValueAsString(metadata);
            Files.write(metadataFilePath, json.getBytes(StandardCharsets.UTF_8), 
                    StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
            
            logger.debug("保存文件名映射: {} -> {}", fileName, originalName);
        } catch (Exception e) {
            logger.warn("保存文件名映射失败: {} -> {}, error={}", fileName, originalName, e.getMessage(), e);
            // 即使保存失败，也不影响文件上传
        }
    }

    /**
     * 从元数据文件加载文件名映射
     */
    private Map<String, String> loadFileNameMapping() {
        try {
            if (metadataFilePath != null && Files.exists(metadataFilePath)) {
                byte[] bytes = Files.readAllBytes(metadataFilePath);
                String json = new String(bytes, StandardCharsets.UTF_8);
                
                if (json != null && !json.trim().isEmpty()) {
                    Map<String, String> metadata = objectMapper.readValue(json, 
                            objectMapper.getTypeFactory().constructMapType(Map.class, String.class, String.class));
                    // 更新内存缓存
                    fileNameMapping.putAll(metadata);
                    logger.debug("加载文件名映射成功，共 {} 条记录", metadata.size());
                    return metadata;
                } else {
                    logger.warn("文件名映射文件为空");
                }
            } else {
                logger.debug("文件名映射文件不存在: {}", metadataFilePath);
            }
        } catch (Exception e) {
            logger.warn("加载文件名映射失败", e);
        }
        return new HashMap<>(fileNameMapping);
    }

    /**
     * 获取原始文件名（公开方法，供Controller使用）
     */
    public String getOriginalFileName(String fileName) {
        // 先从内存缓存获取
        String originalName = fileNameMapping.get(fileName);
        if (originalName != null) {
            return originalName;
        }
        
        // 从元数据文件加载
        Map<String, String> metadata = loadFileNameMapping();
        originalName = metadata.get(fileName);
        
        if (originalName != null) {
            fileNameMapping.put(fileName, originalName);
            return originalName;
        }
        
        // 如果找不到映射，返回文件名本身（去掉UUID部分，保留扩展名）
        return fileName;
    }

    /**
     * 加载文件
     */
    public byte[] loadFile(String fileName) throws IOException {
        Path filePath = Paths.get(uploadDir).resolve(fileName);

        // 安全检查：防止路径遍历攻击
        if (!isSafeFilePath(filePath)) {
            logger.warn("非法文件路径访问尝试: {}", fileName);
            throw new SecurityException("非法文件路径访问");
        }

        if (!Files.exists(filePath)) {
            logger.warn("文件不存在: {}", filePath.toAbsolutePath());
            throw new IOException("文件不存在: " + fileName);
        }

        long fileSize = Files.size(filePath);
        logger.debug("加载文件: {}, 路径: {}, 大小: {} bytes", fileName, filePath.toAbsolutePath(), fileSize);
        
        if (fileSize == 0) {
            logger.warn("文件大小为0: {}", fileName);
            throw new IOException("文件大小为0: " + fileName);
        }
        
        byte[] fileData = Files.readAllBytes(filePath);
        logger.debug("文件加载成功: {}, 读取大小: {} bytes", fileName, fileData.length);
        
        if (fileData.length == 0) {
            logger.error("读取的文件数据为空: {}", fileName);
            throw new IOException("读取的文件数据为空: " + fileName);
        }
        
        return fileData;
    }

    /**
     * 删除文件（同时删除文件系统和数据库记录）
     */
    public boolean deleteFile(String fileName) {
        try {
            Path filePath = Paths.get(uploadDir).resolve(fileName);

            // 安全检查：防止路径遍历攻击
            if (!isSafeFilePath(filePath)) {
                logger.warn("非法文件删除尝试: {}", fileName);
                throw new SecurityException("非法文件路径访问");
            }

            // 先查询数据库记录，获取MD5和原始文件名（用于清理分片文件和删除所有相关记录）
            String fileMd5 = null;
            String originalName = null;
            try {
                com.childenglish.entity.FileInfo fileInfo = fileInfoMapper.selectByFileName(fileName);
                if (fileInfo != null) {
                    fileMd5 = fileInfo.getFileMd5();
                    originalName = fileInfo.getOriginalName();
                    logger.info("查询到文件信息: fileName={}, fileMd5={}, originalName={}", fileName, fileMd5, originalName);
                }
            } catch (Exception e) {
                logger.warn("查询文件信息失败: fileName={}, error={}", fileName, e.getMessage());
            }
            
            // 如果通过fileName查询不到，尝试从文件名提取MD5（如果文件名是MD5格式）
            if (fileMd5 == null || fileMd5.isEmpty()) {
                if (fileName.length() >= 32) {
                    String nameWithoutExt = fileName.contains(".") ? 
                        fileName.substring(0, fileName.lastIndexOf('.')) : fileName;
                    if (nameWithoutExt.length() == 32 && nameWithoutExt.matches("[0-9a-f]{32}")) {
                        fileMd5 = nameWithoutExt;
                        logger.info("从文件名提取MD5: fileName={}, fileMd5={}", fileName, fileMd5);
                    }
                }
            }
            
            // 如果仍然没有MD5，尝试通过原始文件名查询（从文件名映射获取）
            if ((fileMd5 == null || fileMd5.isEmpty()) && originalName == null) {
                try {
                    originalName = getOriginalFileName(fileName);
                    if (originalName != null && !originalName.equals(fileName)) {
                        // 尝试通过原始文件名查找所有相关记录
                        List<com.childenglish.entity.FileInfo> allFiles = fileInfoMapper.selectAll(null);
                        for (com.childenglish.entity.FileInfo info : allFiles) {
                            if (originalName.equals(info.getOriginalName())) {
                                if (fileMd5 == null && info.getFileMd5() != null) {
                                    fileMd5 = info.getFileMd5();
                                    logger.info("通过原始文件名找到MD5: originalName={}, fileMd5={}", originalName, fileMd5);
                                    break;
                                }
                            }
                        }
                    }
                } catch (Exception e) {
                    logger.debug("通过原始文件名查询失败: fileName={}, error={}", fileName, e.getMessage());
                }
            }
            
            // 先删除所有相同MD5的记录（防止重复上传检测，必须在删除fileName之前）
            if (fileMd5 != null && !fileMd5.isEmpty()) {
                try {
                    // 删除所有相同MD5的记录（包括可能存在的其他记录）
                    int md5DeletedCount = fileInfoMapper.deleteByFileMd5(fileMd5);
                    if (md5DeletedCount > 0) {
                        logger.info("✅ 删除所有相同MD5的记录: fileMd5={}, deletedCount={}", fileMd5, md5DeletedCount);
                    } else {
                        logger.warn("⚠️ 删除MD5记录时未找到记录: fileMd5={}", fileMd5);
                    }
                } catch (Exception e) {
                    logger.error("❌ 删除MD5相关记录失败: fileMd5={}, error={}", fileMd5, e.getMessage(), e);
                }
            }
            
            // 删除数据库记录（删除fileName对应的记录，即使MD5已删除也要删除，以防万一）
            try {
                int deletedCount = fileInfoMapper.deleteByFileName(fileName);
                if (deletedCount > 0) {
                    logger.info("✅ 数据库记录删除成功: fileName={}, deletedCount={}", fileName, deletedCount);
                } else {
                    logger.warn("⚠️ 数据库中没有找到文件记录: fileName={}", fileName);
                }
            } catch (Exception e) {
                logger.error("❌ 删除数据库记录失败: fileName={}, error={}", fileName, e.getMessage(), e);
                // 数据库删除失败不影响文件删除
            }
            
            // 如果知道原始文件名，也尝试通过原始文件名删除（双重保险）
            if (originalName != null && !originalName.equals(fileName)) {
                try {
                    int originalNameDeletedCount = fileInfoMapper.deleteByOriginalName(originalName);
                    if (originalNameDeletedCount > 0) {
                        logger.info("✅ 通过原始文件名删除记录: originalName={}, deletedCount={}", originalName, originalNameDeletedCount);
                    }
                } catch (Exception e) {
                    logger.warn("通过原始文件名删除记录失败: originalName={}, error={}", originalName, e.getMessage());
                }
            }
            
            // 清理分片文件
            if (fileMd5 != null && !fileMd5.isEmpty()) {
                try {
                    fileChunkService.cleanupChunks(fileMd5);
                    logger.info("✅ 分片文件清理成功: fileMd5={}", fileMd5);
                } catch (Exception e) {
                    logger.warn("清理分片文件失败: fileMd5={}, error={}", fileMd5, e.getMessage());
                }
            }

            // 删除文件系统中的文件
            boolean deleted = Files.deleteIfExists(filePath);
            if (deleted) {
                logger.info("文件删除成功: {}", fileName);
                
                // 删除文件名映射
                try {
                    Map<String, String> metadata = loadFileNameMapping();
                    metadata.remove(fileName);
                    if (metadataFilePath != null) {
                        String json = objectMapper.writeValueAsString(metadata);
                        Files.write(metadataFilePath, json.getBytes(StandardCharsets.UTF_8), 
                                StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
                    }
                    fileNameMapping.remove(fileName);
                } catch (Exception e) {
                    logger.warn("删除文件名映射失败: fileName={}, error={}", fileName, e.getMessage());
                }
            } else {
                logger.warn("文件删除失败，文件可能不存在: {}", fileName);
            }
            return deleted;
        } catch (IOException e) {
            logger.error("删除文件失败: {}", fileName, e);
            return false;
        } catch (SecurityException e) {
            logger.error("安全异常: {}", fileName, e);
            return false;
        }
    }

    /**
     * 文件验证 - 增强安全检查（重载方法，接受字节数组）
     */
    private void validateFile(MultipartFile file, byte[] fileBytes) throws IOException {
        if (file == null || file.isEmpty()) {
            throw new IOException("文件不能为空");
        }

        // 检查文件大小
        if (file.getSize() > maxFileSize) {
            throw new IOException("文件大小不能超过" + (maxFileSize / 1024 / 1024) + "MB");
        }

        // 检查文件扩展名
        String originalFileName = file.getOriginalFilename();
        if (originalFileName == null || originalFileName.contains("..")) {
            throw new SecurityException("文件名包含非法字符");
        }

        String fileExtension = getFileExtension(originalFileName).toLowerCase();
        if (!ALLOWED_EXTENSIONS.contains(fileExtension)) {
            throw new IOException("不支持的文件类型: " + fileExtension);
        }

        // 检查MIME类型（支持带参数的MIME类型，如 audio/webm; codecs=opus）
        String contentType = file.getContentType();
        if (contentType != null) {
            // 提取基础MIME类型（去掉参数部分）
            String baseContentType = contentType.split(";")[0].trim().toLowerCase();
            // 检查是否在允许的MIME类型列表中（支持完整匹配和基础类型匹配）
            boolean isAllowed = ALLOWED_MIME_TYPES.stream()
                    .anyMatch(allowed -> {
                        String baseAllowed = allowed.split(";")[0].trim().toLowerCase();
                        return baseAllowed.equals(baseContentType) || allowed.equalsIgnoreCase(contentType);
                    });
            
            if (!isAllowed) {
                throw new IOException("不支持的文件格式: " + contentType);
            }
        } else {
            // 如果contentType为null，只检查文件扩展名
            logger.warn("文件contentType为null，仅通过扩展名验证: {}", originalFileName);
        }

        // 多层安全检查（使用字节数组创建临时MultipartFile进行验证）
        // 注意：这里简化安全检查，避免多次读取流
        // 只进行基本的扩展名和MIME类型检查，更深入的检查在文件保存后进行
        
        logger.debug("文件验证通过: {} ({}), 大小: {} bytes",
                originalFileName, contentType, file.getSize());
    }

    /**
     * 文件验证 - 增强安全检查（保留原方法以兼容其他调用）
     */
    private void validateFile(MultipartFile file) throws IOException {
        // 先读取文件内容
        byte[] fileBytes;
        try {
            fileBytes = file.getBytes();
        } catch (IOException e) {
            throw new IOException("读取文件内容失败: " + e.getMessage());
        }
        
        // 调用重载方法
        validateFile(file, fileBytes);
    }

    /**
     * 获取文件扩展名
     */
    private String getFileExtension(String fileName) {
        if (fileName == null || fileName.lastIndexOf(".") == -1) {
            return "dat"; // 默认扩展名
        }
        return fileName.substring(fileName.lastIndexOf(".") + 1);
    }

    /**
     * 安全检查：防止路径遍历攻击
     */
    private boolean isSafeFilePath(Path filePath) {
        try {
            Path normalizedPath = filePath.normalize();
            Path uploadDirPath = Paths.get(uploadDir).normalize();
            return normalizedPath.startsWith(uploadDirPath) &&
                    !normalizedPath.equals(uploadDirPath);
        } catch (Exception e) {
            logger.error("文件路径安全检查异常", e);
            return false;
        }
    }

    /**
     * 获取文件信息
     */
    public Map<String, Object> getFileInfo(String fileName) throws IOException {
        Path filePath = Paths.get(uploadDir).resolve(fileName);

        // 安全检查
        if (!isSafeFilePath(filePath)) {
            throw new SecurityException("非法文件路径访问");
        }

        if (!Files.exists(filePath)) {
            throw new IOException("文件不存在: " + fileName);
        }

        Map<String, Object> fileInfo = new HashMap<>();
        fileInfo.put("fileName", fileName);
        fileInfo.put("size", Files.size(filePath));
        fileInfo.put("lastModified", Files.getLastModifiedTime(filePath).toMillis());
        fileInfo.put("isRegularFile", Files.isRegularFile(filePath));
        fileInfo.put("isReadable", Files.isReadable(filePath));
        fileInfo.put("isWritable", Files.isWritable(filePath));

        try {
            fileInfo.put("mimeType", Files.probeContentType(filePath));
        } catch (IOException e) {
            fileInfo.put("mimeType", "unknown");
        }

        logger.debug("获取文件信息: {}", fileName);
        return fileInfo;
    }

    /**
     * 清理临时文件 - 定时任务
     */
    @Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行
    public void cleanupOldFiles() {
        if (!cleanupEnabled) {
            logger.debug("文件清理功能已禁用");
            return;
        }

        try {
            Path uploadPath = Paths.get(uploadDir);
            if (!Files.exists(uploadPath)) {
                return;
            }

            long currentTime = System.currentTimeMillis();
            int cleanedCount = 0;

            // 使用 try-with-resources 确保 Stream 正确关闭
            try (Stream<Path> files = Files.list(uploadPath)) {
                cleanedCount = (int) files
                        .filter(Files::isRegularFile)
                        .filter(path -> {
                            try {
                                long fileAge = currentTime - Files.getLastModifiedTime(path).toMillis();
                                return fileAge > cleanupAge;
                            } catch (IOException e) {
                                logger.warn("无法获取文件修改时间: {}", path.getFileName(), e);
                                return false;
                            }
                        })
                        .mapToInt(path -> {
                            try {
                                Files.deleteIfExists(path);
                                logger.info("清理文件: {}", path.getFileName());
                                return 1;
                            } catch (IOException e) {
                                logger.error("清理文件失败: {}", path.getFileName(), e);
                                return 0;
                            }
                        })
                        .sum();
            }

            logger.info("文件清理完成，共清理 {} 个文件", cleanedCount);

        } catch (IOException e) {
            logger.error("文件清理过程发生异常", e);
        }
    }

    /**
     * 获取存储目录信息
     */
    public Map<String, Object> getStorageInfo() {
        Map<String, Object> storageInfo = new HashMap<>();

        try {
            Path uploadPath = Paths.get(uploadDir);
            if (Files.exists(uploadPath)) {
                try (Stream<Path> files = Files.list(uploadPath)) {
                    long fileCount = files.filter(Files::isRegularFile).count();
                    storageInfo.put("fileCount", fileCount);
                }

                // 计算目录大小（简化版）
                try (Stream<Path> files = Files.walk(uploadPath)) {
                    long totalSize = files
                            .filter(Files::isRegularFile)
                            .mapToLong(path -> {
                                try {
                                    return Files.size(path);
                                } catch (IOException e) {
                                    return 0L;
                                }
                            })
                            .sum();
                    storageInfo.put("totalSize", totalSize);
                    storageInfo.put("totalSizeMB", totalSize / 1024 / 1024);
                }
            } else {
                storageInfo.put("fileCount", 0);
                storageInfo.put("totalSize", 0);
                storageInfo.put("totalSizeMB", 0);
            }

            storageInfo.put("uploadDir", uploadPath.toAbsolutePath().toString());
            storageInfo.put("maxFileSize", maxFileSize);
            storageInfo.put("maxFileSizeMB", maxFileSize / 1024 / 1024);
            storageInfo.put("cleanupEnabled", cleanupEnabled);
            storageInfo.put("cleanupAge", cleanupAge);
            storageInfo.put("allowedExtensions", ALLOWED_EXTENSIONS);

        } catch (IOException e) {
            logger.error("获取存储信息失败", e);
            storageInfo.put("error", e.getMessage());
        }

        return storageInfo;
    }

    /**
     * 手动触发文件清理
     */
    public Map<String, Object> manualCleanup() {
        Map<String, Object> result = new HashMap<>();
        result.put("timestamp", System.currentTimeMillis());
        result.put("cleanupEnabled", cleanupEnabled);

        if (cleanupEnabled) {
            try {
                cleanupOldFiles();
                result.put("status", "success");
                result.put("message", "手动清理完成");
            } catch (Exception e) {
                result.put("status", "error");
                result.put("message", e.getMessage());
            }
        } else {
            result.put("status", "skipped");
            result.put("message", "文件清理功能已禁用");
        }

        return result;
    }

    /**
     * 获取文件列表（合并数据库和文件系统的数据）
     * @param type 文件类型筛选（可选）：courseware（课件）、picturebook（绘本）
     * @return 文件列表
     */
    public List<Map<String, Object>> getFileList(String type) throws IOException {
        // 从数据库读取文件信息（包含审核状态）
        Map<String, Map<String, Object>> dbFileMap = new HashMap<>();
        try {
            List<com.childenglish.entity.FileInfo> dbFiles = null;
            if (type != null && !type.isEmpty()) {
                // 根据类型查询（查询所有状态的文件，前端会根据角色筛选）
                dbFiles = fileInfoMapper.selectByReviewStatus(null, type);
            } else {
                // 查询所有文件
                dbFiles = fileInfoMapper.selectByStatus(2); // 合并完成的文件
            }
            
            if (dbFiles != null && !dbFiles.isEmpty()) {
                for (com.childenglish.entity.FileInfo fileInfo : dbFiles) {
                    Map<String, Object> fileMap = new HashMap<>();
                    fileMap.put("id", fileInfo.getId());
                    fileMap.put("fileName", fileInfo.getFileName());
                    fileMap.put("originalName", fileInfo.getOriginalName());
                    fileMap.put("name", fileInfo.getOriginalName());
                    fileMap.put("displayName", fileInfo.getOriginalName());
                    fileMap.put("size", fileInfo.getFileSize());
                    fileMap.put("uploadTime", fileInfo.getUploadTime() != null ? fileInfo.getUploadTime().getTime() : System.currentTimeMillis());
                    fileMap.put("type", fileInfo.getFileType());
                    fileMap.put("mimeType", fileInfo.getMimeType());
                    fileMap.put("extension", getFileExtension(fileInfo.getOriginalName()));
                    fileMap.put("reviewStatus", fileInfo.getReviewStatus() != null ? fileInfo.getReviewStatus() : 0);
                    fileMap.put("reviewReason", fileInfo.getReviewReason());
                    fileMap.put("reviewer", fileInfo.getReviewer());
                    fileMap.put("reviewTime", fileInfo.getReviewTime() != null ? fileInfo.getReviewTime().getTime() : null);
                    fileMap.put("uploadUser", fileInfo.getUploadUser());
                    // 使用fileName作为key，因为这是唯一标识
                    dbFileMap.put(fileInfo.getFileName(), fileMap);
                }
                logger.debug("从数据库获取文件列表，共 {} 个文件", dbFileMap.size());
            }
        } catch (Exception e) {
            logger.warn("从数据库读取文件列表失败，将只使用文件系统数据: {}", e.getMessage());
        }
        
        // 从文件系统获取文件列表
        List<Map<String, Object>> fsFileList = getFileListFromFileSystem(type);
        
        // 合并数据库和文件系统的数据
        // 优先使用数据库中的数据（包含审核状态等信息），文件系统中存在但数据库中没有的文件也添加
        List<Map<String, Object>> mergedFileList = new ArrayList<>();
        
        // 先添加数据库中的文件（需要再次按类型筛选，确保类型正确）
        for (Map<String, Object> dbFile : dbFileMap.values()) {
            // 如果指定了类型筛选，确保文件类型匹配
            if (type != null && !type.isEmpty()) {
                String fileType = (String) dbFile.get("type");
                if (!type.equals(fileType)) {
                    logger.debug("跳过文件，类型不匹配: fileName={}, 期望类型={}, 实际类型={}", 
                        dbFile.get("fileName"), type, fileType);
                    continue;
                }
            }
            mergedFileList.add(dbFile);
        }
        
        // 添加文件系统中存在但数据库中没有的文件，并自动同步到数据库
        for (Map<String, Object> fsFile : fsFileList) {
            String fileName = (String) fsFile.get("fileName");
            if (!dbFileMap.containsKey(fileName)) {
                // 文件系统中存在但数据库中没有，尝试同步到数据库
                try {
                    com.childenglish.entity.FileInfo fileInfo = new com.childenglish.entity.FileInfo();
                    fileInfo.setFileName(fileName);
                    fileInfo.setOriginalName((String) fsFile.get("originalName"));
                    fileInfo.setFileSize((Long) fsFile.get("size"));
                    
                    // 确定文件类型：优先使用查询类型（如果指定），否则使用数据库中的类型
                    // 注意：文件系统同步时，只同步与查询类型匹配的文件
                    String fileType = null;
                    if (type != null && !type.isEmpty()) {
                        // 如果指定了查询类型，使用该类型
                        fileType = type;
                    } else {
                        // 否则从文件扩展名推断（兼容旧数据）
                        String originalName = fileInfo.getOriginalName();
                        String nameToCheck = originalName != null ? originalName : fileName;
                        if (nameToCheck != null && nameToCheck.contains(".")) {
                            String extension = nameToCheck.substring(nameToCheck.lastIndexOf('.') + 1).toLowerCase();
                            if (Arrays.asList("pdf", "ppt", "pptx", "doc", "docx", "txt").contains(extension)) {
                                fileType = "courseware";
                            } else if (Arrays.asList("jpg", "jpeg", "png", "gif", "webp").contains(extension)) {
                                fileType = "picturebook";
                            } else if (Arrays.asList("mp3", "wav", "ogg", "webm", "m4a", "mp4", "aac", "flac").contains(extension)) {
                                fileType = "audio";
                            } else {
                                fileType = "courseware";
                            }
                        } else {
                            fileType = "courseware";
                        }
                    }
                    fileInfo.setFileType(fileType);
                    fileInfo.setMimeType((String) fsFile.get("mimeType"));
                    
                    // 设置文件路径
                    Path filePath = Paths.get(uploadDir, fileName);
                    fileInfo.setFilePath(filePath.toString());
                    
                    // 尝试从文件名提取MD5（如果文件名是MD5格式）
                    if (fileName.length() == 32 && fileName.matches("[0-9a-f]{32}")) {
                        fileInfo.setFileMd5(fileName.substring(0, fileName.lastIndexOf('.')));
                    } else if (fileName.contains(".")) {
                        String nameWithoutExt = fileName.substring(0, fileName.lastIndexOf('.'));
                        if (nameWithoutExt.length() == 32 && nameWithoutExt.matches("[0-9a-f]{32}")) {
                            fileInfo.setFileMd5(nameWithoutExt);
                        }
                    }
                    
                    fileInfo.setStatus(2); // 合并完成
                    fileInfo.setReviewStatus(0); // 待审核
                    fileInfo.setChunked(0);
                    
                    // 设置上传时间
                    Long uploadTime = (Long) fsFile.get("uploadTime");
                    if (uploadTime != null) {
                        fileInfo.setUploadTime(new Date(uploadTime));
                        fileInfo.setCompleteTime(new Date(uploadTime));
                    } else {
                        Date now = new Date();
                        fileInfo.setUploadTime(now);
                        fileInfo.setCompleteTime(now);
                    }
                    
                    // 插入数据库
                    int insertResult = fileInfoMapper.insert(fileInfo);
                    if (insertResult > 0) {
                        // 插入成功，从数据库重新查询以获取完整信息（包括ID）
                        com.childenglish.entity.FileInfo insertedFile = fileInfoMapper.selectByFileName(fileName);
                        if (insertedFile != null) {
                            // 使用数据库中的完整信息
                            Map<String, Object> syncedFileMap = new HashMap<>();
                            syncedFileMap.put("id", insertedFile.getId());
                            syncedFileMap.put("fileName", insertedFile.getFileName());
                            syncedFileMap.put("originalName", insertedFile.getOriginalName());
                            syncedFileMap.put("name", insertedFile.getOriginalName());
                            syncedFileMap.put("displayName", insertedFile.getOriginalName());
                            syncedFileMap.put("size", insertedFile.getFileSize());
                            syncedFileMap.put("uploadTime", insertedFile.getUploadTime() != null ? insertedFile.getUploadTime().getTime() : System.currentTimeMillis());
                            syncedFileMap.put("type", insertedFile.getFileType());
                            syncedFileMap.put("mimeType", insertedFile.getMimeType());
                            syncedFileMap.put("extension", getFileExtension(insertedFile.getOriginalName()));
                            syncedFileMap.put("reviewStatus", insertedFile.getReviewStatus() != null ? insertedFile.getReviewStatus() : 0);
                            syncedFileMap.put("reviewReason", insertedFile.getReviewReason());
                            syncedFileMap.put("reviewer", insertedFile.getReviewer());
                            syncedFileMap.put("reviewTime", insertedFile.getReviewTime() != null ? insertedFile.getReviewTime().getTime() : null);
                            syncedFileMap.put("uploadUser", insertedFile.getUploadUser());
                            mergedFileList.add(syncedFileMap);
                            // 同时更新dbFileMap，避免重复处理
                            dbFileMap.put(fileName, syncedFileMap);
                            logger.info("成功同步文件到数据库: fileName={}, id={}, type={}", fileName, insertedFile.getId(), fileType);
                        } else {
                            // 插入成功但查询不到，使用文件系统数据
                            fsFile.put("reviewStatus", 0);
                            fsFile.put("reviewReason", null);
                            fsFile.put("reviewer", null);
                            fsFile.put("reviewTime", null);
                            fsFile.put("id", null);
                            fsFile.put("type", fileType);
                            // 再次检查类型是否匹配
                            if (type == null || type.isEmpty() || type.equals(fileType)) {
                                mergedFileList.add(fsFile);
                            } else {
                                logger.warn("文件类型不匹配，跳过: fileName={}, 期望类型={}, 实际类型={}", fileName, type, fileType);
                            }
                            logger.warn("文件已插入数据库但查询不到: fileName={}", fileName);
                        }
                    } else {
                        // 插入失败，使用文件系统数据
                        fsFile.put("reviewStatus", 0);
                        fsFile.put("reviewReason", null);
                        fsFile.put("reviewer", null);
                        fsFile.put("reviewTime", null);
                        fsFile.put("id", null);
                        fsFile.put("type", fileType);
                        // 再次检查类型是否匹配
                        if (type == null || type.isEmpty() || type.equals(fileType)) {
                            mergedFileList.add(fsFile);
                        } else {
                            logger.warn("文件类型不匹配，跳过: fileName={}, 期望类型={}, 实际类型={}", fileName, type, fileType);
                        }
                        logger.warn("同步文件到数据库失败: fileName={}, error=插入返回0", fileName);
                    }
                } catch (Exception e) {
                    // 同步失败，使用文件系统数据
                    String fileType = (String) fsFile.get("type");
                    if (fileType == null || fileType.isEmpty()) {
                        fileType = type != null && !type.isEmpty() ? type : "courseware";
                    }
                    fsFile.put("reviewStatus", 0);
                    fsFile.put("reviewReason", null);
                    fsFile.put("reviewer", null);
                    fsFile.put("reviewTime", null);
                    fsFile.put("id", null);
                    fsFile.put("type", fileType);
                    // 再次检查类型是否匹配
                    if (type == null || type.isEmpty() || type.equals(fileType)) {
                        mergedFileList.add(fsFile);
                    } else {
                        logger.warn("文件类型不匹配，跳过: fileName={}, 期望类型={}, 实际类型={}", fileName, type, fileType);
                    }
                    logger.error("同步文件到数据库时发生异常: fileName={}, error={}", fileName, e.getMessage(), e);
                }
            }
        }
        
        // 按上传时间倒序排序
        mergedFileList.sort((a, b) -> {
            Long timeA = (Long) a.get("uploadTime");
            Long timeB = (Long) b.get("uploadTime");
            if (timeA == null) timeA = 0L;
            if (timeB == null) timeB = 0L;
            return timeB.compareTo(timeA);
        });
        
        logger.debug("合并后的文件列表，共 {} 个文件（类型筛选: {}）", mergedFileList.size(), type);
        return mergedFileList;
    }

    /**
     * 从文件系统获取文件列表（原有逻辑）
     */
    public List<Map<String, Object>> getFileListFromFileSystem(String type) throws IOException {
        List<Map<String, Object>> fileList = new java.util.ArrayList<>();
        
        logger.debug("开始从文件系统获取文件列表，上传目录: {}, 类型筛选: {}", uploadDir, type);
        
        try {
            Path uploadPath = Paths.get(uploadDir);
            if (!Files.exists(uploadPath)) {
                logger.warn("上传目录不存在: {}", uploadPath.toAbsolutePath());
                return fileList;
            }
            
            logger.debug("上传目录存在: {}", uploadPath.toAbsolutePath());

            // 排除元数据文件
            final String metadataFileName = ".file-metadata.json";

            try (Stream<Path> files = Files.list(uploadPath)) {
                long totalFiles = files.filter(Files::isRegularFile)
                    .filter(path -> !path.getFileName().toString().equals(metadataFileName))
                    .count();
                logger.debug("上传目录中共有 {} 个文件（已排除元数据文件）", totalFiles);
            }
            
            try (Stream<Path> files = Files.list(uploadPath)) {
                files.filter(Files::isRegularFile)
                    .filter(path -> !path.getFileName().toString().equals(metadataFileName)) // 排除元数据文件
                    .forEach(path -> {
                        try {
                            String fileName = path.getFileName().toString();
                            String fileExtension = getFileExtension(fileName).toLowerCase();
                            
                            logger.debug("检查文件: fileName={}, extension={}", fileName, fileExtension);
                            
                            // 根据类型筛选（从数据库查询文件类型，不限制文件扩展名）
                            if (type != null && !type.isEmpty()) {
                                // 先查询数据库中的文件类型
                                String dbFileType = null;
                                try {
                                    com.childenglish.entity.FileInfo dbFile = fileInfoMapper.selectByFileName(fileName);
                                    if (dbFile != null) {
                                        dbFileType = dbFile.getFileType();
                                    }
                                } catch (Exception e) {
                                    logger.debug("查询文件类型失败: fileName={}, error={}", fileName, e.getMessage());
                                }
                                
                                // 如果数据库中有该文件，使用数据库中的类型；否则跳过（等待同步）
                                if (dbFileType != null && !dbFileType.isEmpty()) {
                                    if (!type.equals(dbFileType)) {
                                        logger.debug("文件类型不匹配，跳过: fileName={}, 数据库类型={}, 查询类型={}", 
                                            fileName, dbFileType, type);
                                        return;
                                    }
                                } else {
                                    // 数据库中没有该文件，跳过（等待同步时再处理）
                                    logger.debug("文件不在数据库中，跳过: fileName={}", fileName);
                                        return;
                                }
                            }

                            // 获取原始文件名（确保元数据文件已加载）
                            if (metadataFilePath == null) {
                                metadataFilePath = uploadPath.resolve(".file-metadata.json");
                                loadFileNameMapping();
                            }
                            String originalName = getOriginalFileName(fileName);
                            
                            // 如果找不到映射，记录警告
                            if (originalName.equals(fileName)) {
                                logger.warn("未找到文件 {} 的原始文件名映射，使用UUID文件名", fileName);
                            }
                            
                            Map<String, Object> fileInfo = new HashMap<>();
                            fileInfo.put("fileName", fileName); // 保存的文件名（UUID）
                            fileInfo.put("originalName", originalName); // 原始文件名
                            fileInfo.put("name", originalName); // 显示名称使用原始文件名
                            fileInfo.put("size", Files.size(path));
                            fileInfo.put("uploadTime", Files.getLastModifiedTime(path).toMillis());
                            // 如果指定了类型筛选，使用筛选类型；否则使用通用类型
                            if (type != null && !type.isEmpty()) {
                                fileInfo.put("type", type);
                            } else {
                            fileInfo.put("type", getFileType(fileExtension));
                            }
                            fileInfo.put("extension", fileExtension);
                            
                            logger.debug("添加文件到列表: fileName={}, originalName={}, extension={}, type={}, size={}", 
                                fileName, originalName, fileExtension, fileInfo.get("type"), fileInfo.get("size"));
                            
                            try {
                                fileInfo.put("mimeType", Files.probeContentType(path));
                            } catch (IOException e) {
                                fileInfo.put("mimeType", "unknown");
                            }

                            fileList.add(fileInfo);
                        } catch (IOException e) {
                            logger.warn("获取文件信息失败: {}", path.getFileName(), e);
                        }
                    });
            }
        } catch (IOException e) {
            logger.error("获取文件列表失败", e);
            throw e;
        }

        // 按上传时间倒序排序
        fileList.sort((a, b) -> {
            Long timeA = (Long) a.get("uploadTime");
            Long timeB = (Long) b.get("uploadTime");
            return timeB.compareTo(timeA);
        });

        logger.debug("文件列表获取完成，共 {} 个文件（类型筛选: {}）", fileList.size(), type);

        return fileList;
    }

    /**
     * 根据文件扩展名获取文件类型
     */
    private String getFileType(String extension) {
        if (Arrays.asList("pdf", "ppt", "pptx", "doc", "docx", "txt").contains(extension)) {
            return "document";
        } else if (Arrays.asList("jpg", "jpeg", "png", "gif", "webp").contains(extension)) {
            return "image";
        } else if (Arrays.asList("mp3", "wav", "ogg", "webm", "m4a").contains(extension)) {
            return "audio";
        } else if (Arrays.asList("mp4", "avi", "mov").contains(extension)) {
            return "video";
        }
        return "other";
    }
}